package Crypto.xor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;


public class XOrCrypto {
	/**
	 * encrypt
	 * @param byteFile
	 * @param keyFile
	 * @param outFile
	 * @throws IOException
	 * 
	 * encrypts bytefile using keyfile as the key and places the ciphertext
	 * to the outFile location.
	 */
	public static void encrypt(String byteFile, String keyFile, String outFile)
			throws IOException {
		byte[] key = XOrCrypto.getBytesFromFile(keyFile),
			   plaintext = getBytesFromFile(byteFile);
		
		/* Encrypt our plaintext */
		byte [] cipherText = encryptByteArray(key,plaintext);
		/* Save the file to the disk */
		XOrCrypto.saveToFile(outFile, cipherText);
	}
	
	/**
	 * encryptStdOut
	 * @param msgIn
	 * @param keyIn
	 * @throws IOException
	 * 
	 * This function is still in development.
	 * Encrypts a message with the given key and outputs the result to standard
	 * out in ASCII string encoding. This eliminates writes out to the hard
	 * disk.
	 * 
	 * Java's limitation on string size is a major limiting factor.
	 */
	public static void encryptStdOut(String msgIn, String keyIn) throws IOException {
		/* Convert our ASCII string integer value key to a byte array */
		byte[] key = btoS.convertStringToByteArray(keyIn),
			   plaintext = btoS.convertStringToByteArray(msgIn);
		
		/* Encrypt our message and save the ciphertext as a byte array */
		byte [] cipherText = encryptByteArray(key,plaintext); // encrypt our plaintext
		
		/* Convert our ciphertext string to */
		System.out.print(btoS.convertByteArrayToString(cipherText));
	}
	
	/**
	 * encryptByteArray
	 * @param key
	 * @param plaintext
	 * @return
	 * 
	 * This function actually encrypts the given byte array by iterating
	 * through each of the indices of the plaintext.
	 */
	private static byte[] encryptByteArray(byte[] key, byte[] plaintext) {
		/* create our output array */
		byte []retVal = new byte [plaintext.length];
		int keySize = key.length; // in bytes
		int byteArraySize = plaintext.length; // in bytes
		
		/* Now we loop through our byte array*/
		for(int i = 0; i < byteArraySize; i+=keySize) {
			int end = (i + keySize - 1);
			if(end > plaintext.length) {
				end = plaintext.length - 1;
			}
			for(int j = i; j <= end; j++) {
				/* Perform our XOr operation*/
				retVal[j] = (byte) (plaintext[j] ^ key[(j % key.length)]);
			}
		}
		
		return retVal;
	}

	/**
	 * decrypt
	 * @param byteFile
	 * @param keyFile
	 * @param outFile
	 * @throws IOException
	 * This function uses keyfile to decrypt bytefile and stores the output in
	 * outfile
	 */
	public static void decrypt(String byteFile, String keyFile, String outFile)
			throws IOException {
		/* Load our key */
		byte[] key = XOrCrypto.getBytesFromFile(keyFile),
			   ciphertext = getBytesFromFile(byteFile);
		
		/* Generate the ciphertext*/
		byte [] cipherText = decryptByteArray(key,ciphertext);
		
		/* Write the ciphertext to disk */
		FileManipulation.writeFileToDisk(cipherText, outFile);
	}
	
	/**
	 * decryptStdOut
	 * @param msgIn
	 * @param keyIn
	 * @throws IOException
	 * 
	 * This function is still in development.
	 * Decrypts a message with the given key and outputs the result to standard
	 * out in ASCII string encoding. This eliminates writes out to the hard
	 * disk.
	 * 
	 * Java's limitation on string size is a major limiting factor.
	 */
	public static void decryptStdOut(String cipherIn, String keyIn) 
			throws IOException {
		/* Load our key */
		byte[] key = btoS.convertStringToByteArray(keyIn),
			   ciphertext = btoS.convertStringToByteArray(cipherIn);
		
		/* Decrypt the ciphertext */
		byte [] plainText = decryptByteArray(key,ciphertext);

		/* Output to standard out */
		System.out.println(btoS.convertByteArrayToString(plainText));
	}
	
	/**
	 * generateKey
	 * @param keySize
	 * @param filename
	 * @throws IOException
	 * 
	 * Generates a key of keySize bytes and stores it at the filename location
	 */
	public static void generateKey(int keySize, String filename)
			throws IOException {
		XOrCrypto.saveToFile(filename, XOrCrypto.generateKeyToArray(keySize));
	}
	
	/**
	 * generateKeyStdOut
	 * @param keySize
	 * @throws IOException
	 * Generates a key to standard out
	 */
	public static void generateKeyStdOut(int keySize) throws IOException {
		System.out.print(btoS.convertByteArrayToString(
				XOrCrypto.generateKeyToArray(keySize)));
	}
	
	/**
	 * decryptByteArray
	 * @param key
	 * @param ciphertext
	 * @return
	 * Performs the lower level byte array decryption by looping through each
	 * of the indicies of the array.
	 */
	private static byte[] decryptByteArray(byte[] key, byte[] ciphertext) {
		byte []retVal = new byte [ciphertext.length];
		int keySize = key.length; // in bytes
		int byteArraySize = ciphertext.length; // in bytes
		
		for(int i = 0; i < byteArraySize; i+=keySize) {
			int end = (i + keySize - 1);
			if(end > ciphertext.length) {
				end = ciphertext.length - 1;
			}
			for(int j = i; j <= end; j++) {
				retVal[j] = (byte) (ciphertext[j] ^ key[(j % key.length)]);
			}
		}
		
		return retVal;
	}
	
	/**
	 * Generates key to array
	 * @param keySize
	 * @return
	 * Generates a key and outputs it to a byte array
	 */
	private static byte[] generateKeyToArray(int keySize) {
		Random newRand = new Random();
		byte[] bytes = new byte[keySize];
		newRand.nextBytes(bytes);
		
		return bytes;
	}
	
	/**
	 * saveToFile
	 * @param fileName
	 * @param bytearray
	 * @throws IOException
	 * Saves a byte array to a given file
	 */
	private static void saveToFile(String fileName, byte[] bytearray) throws IOException {	
		OutputStream fout= new FileOutputStream(fileName);

        fout.write(bytearray);
		
		fout.flush();
		fout.close();
	}
	
	/**
	 * getBytesFromFile
	 * @param in
	 * @return
	 * @throws IOException
	 * 
	 * Reads a file into a byte array
	 */
	private static byte[] getBytesFromFile(String in) throws IOException {
		File file = new File(in);
		InputStream fin = new FileInputStream(file);
		byte []out = new byte[(int)file.length()];
		
		int offset = 0;
        int numRead = 0;
        while (offset < out.length && (numRead=fin.read(out, offset, out.length-offset)) >= 0) {
            offset += numRead;
        }
        
        fin.close();
	    // Close the input stream and return bytes
	    return out;
	}
}
